/*
 * ByteUtils.java Created on 22 ъМБЮПЭ 2006 Ц., 6:04 To change this template, choose Tools | Template Manager and open
 * the template in the editor.
 */

package org.dicr.util.data;

import java.io.*;
import java.util.*;

import org.dicr.util.data.exc.*;

/**
 * @author Igor A Tarasov &lt;java@dicr.org&gt;
 * @version 060122
 */
public class ByteUtils {

	/**
	 * Translate byte to unsigned value
	 * 
	 * @param val byte value (-127 .. 128)
	 * @return (0.255)
	 */
	public static short unsigned(final byte val) {
		short v = val;
		if (v < 0) v += 0x100;
		return v;
	}

	/**
	 * Translate short to unsigned value
	 * 
	 * @param val value
	 * @return unsigned value
	 */
	public static int unsigned(final short val) {
		int v = val;
		if (v < 0) v += 0x10000;
		return v;
	}

	/**
	 * Translate int to unsigned value.
	 * 
	 * @param val signed value
	 * @return unsigned value
	 */
	public static long unsigned(final int val) {
		long v = val;
		if (v < 0) v += 0x100000000L;
		return v;
	}

	/**
	 * Convert short value to bigendian bytes.
	 * 
	 * @param value signed value
	 * @return unsigned value
	 */
	public static byte[] toBytes(final short value) {
		final byte[] ret = new byte[2];
		ret[0] = (byte) (value >> 8 & 0x0FF);
		ret[1] = (byte) (value & 0x0FF);
		return ret;
	}

	/**
	 * Convert integer to bigendian bytes.
	 * 
	 * @param value integer value
	 * @return byte[4]
	 */
	public static byte[] toBytes(final int value) {
		final byte[] bytes = new byte[4];
		bytes[0] = (byte) (value >> 24 & 0x0FF);
		bytes[1] = (byte) (value >> 16 & 0x0FF);
		bytes[2] = (byte) (value >> 8 & 0x0FF);
		bytes[3] = (byte) (value & 0x0FF);
		return bytes;
	}

	/**
	 * Convert long value to bigendian bytes.
	 * 
	 * @param value long value
	 * @return data[8]
	 */
	public static byte[] toBytes(final long value) {
		final byte[] bytes = new byte[8];
		bytes[0] = (byte) (value >> 56 & 0x0FF);
		bytes[1] = (byte) (value >> 48 & 0x0FF);
		bytes[2] = (byte) (value >> 40 & 0x0FF);
		bytes[3] = (byte) (value >> 32 & 0x0FF);
		bytes[4] = (byte) (value >> 24 & 0x0FF);
		bytes[5] = (byte) (value >> 16 & 0x0FF);
		bytes[6] = (byte) (value >> 8 & 0x0FF);
		bytes[7] = (byte) (value & 0x0FF);
		return bytes;
	}

	/**
	 * Convert bigendian bytes to integer.
	 * 
	 * @param data byte data
	 * @param offset offset in data
	 * @param length length of data (1..4)
	 * @return integer value
	 */
	public static int toInteger(final byte[] data, final int offset, final int length) {
		if (data == null || data.length < 1) throw new IllegalArgumentException("empty data");
		if (offset < 0 || offset > data.length - 1) throw new IllegalArgumentException("offset outside data");
		if (length < 0 || length > 4) throw new IllegalArgumentException("length: " + length);
		if (offset + length > data.length) throw new IllegalArgumentException("length exceed data");
		int ret = 0;
		for (int i = 0; i < length; i++) {
			ret = ret << 8 | data[offset + i] & 0x0FF;
		}
		return ret;
	}

	/**
	 * Convert string value to UTF-8 bytes.
	 * 
	 * @param text string value
	 * @return bytes
	 */
	public static byte[] toBytes(final String text) {
		if (text == null) throw new IllegalArgumentException("null text");
		if (text.isEmpty()) return new byte[0];
		byte[] bytes = null;
		try {
			bytes = text.getBytes("UTF-8");
		} catch (final UnsupportedEncodingException ex) {
			throw new Error("unexpected exception", ex);
		}
		return bytes;
	}

	/**
	 * Convert UTF-8 bytes to string.
	 * 
	 * @param data UTF8 bytes
	 * @param offset offset in data
	 * @param length length in data
	 * @return string value
	 */
	public static String toText(final byte[] data, final int offset, final int length) {
		if (data == null) throw new IllegalArgumentException("null data");
		if (data.length < 1) return "";
		if (offset < 0 || offset > data.length - 1) throw new IllegalArgumentException("offset outside data");
		if (length < 0 || offset + length > data.length) throw new IllegalArgumentException("length outside data");
		String s = null;
		try {
			s = new String(data, offset, length, "UTF-8");
		} catch (final UnsupportedEncodingException ex) {
			throw new Error("unexpected exception", ex);
		}
		return s;
	}

	/**
	 * Convert bytes to HEX string. Convert by printf "%02X%02X%02X....", data[0], data[1], ...
	 * 
	 * @param data bytes
	 * @param offset offset in data
	 * @param length number of data
	 * @return String value of data in form 1A02EC15...
	 */
	public static String toHexString(final byte[] data, final int offset, final int length) {
		if (data == null) throw new IllegalArgumentException("null data");
		if (data.length < 1) return "";
		if (offset > data.length - 1) throw new IllegalArgumentException("offset > data.length");
		if (offset + length > data.length) throw new IllegalArgumentException("length + offset > data.length");
		final StringBuilder sb = new StringBuilder();
		for (int i = offset; i < offset + length; i++) {
			sb.append(String.format("%02X", Byte.valueOf(data[i])));
		}
		return sb.toString();
	}

	/**
	 * Convert HEX string to bytes. Convert by sscanf "%02X%02X%02X..."
	 * 
	 * @param hexString Hex string in form "1A34EC9F..."
	 * @return bytes from string
	 * @see #toHexString(byte[], int, int)
	 */
	public static byte[] fromHexString(final String hexString) {
		if (hexString == null) throw new IllegalArgumentException("null hex string");
		if (hexString.isEmpty()) return new byte[0];
		String val = hexString;
		if ((val.length() & 0x01) == 1) val = "0" + val;
		final int len = val.length() >> 1;
		final byte[] data = new byte[len];
		for (int i = 0; i < len; i++) {
			data[i] = (byte) Integer.parseInt(val.substring(i << 1, i + 1 << 1), 16);
		}
		return data;
	}

	/**
	 * Extend bytes buffer to specified length
	 * 
	 * @param bytes byte buffer to extend
	 * @param len length of new byffer
	 * @return extended buffer, trailing bytes filled by zeroes
	 * @throws DataException if len < bytes current length
	 */
	public static byte[] extendBuffer(final byte[] bytes, final int len) throws DataException {
		if (len < 0) throw new IllegalArgumentException("len: " + len);
		final byte[] ret = new byte[len];
		Arrays.fill(ret, (byte) 0);
		if (bytes != null && bytes.length > 0) {
			if (bytes.length > len) throw new DataException("data field overflow, length=" + bytes.length + " > " + len);
			System.arraycopy(bytes, 0, ret, 0, bytes.length);
		}
		return ret;
	}
}
